package com.neveu.android.ds.groupmessenger;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.telephony.TelephonyManager;
import android.util.Log;

public class NetServent {

//	Vector<Socket> clientSock;
	HashMap<Integer, Socket> clientSock;
	private HashMap<Socket, PrintWriter> writers;
	private HashMap<Socket, ObjectOutputStream> objStrms;
	ServerSocket serverSock;
	private String loopbackIP;
	private int connectPort;
	private MessageListener msgListener;
	private Context mainContext;
	private Uri providerUri;
	private ObjectOutputStream mOutStr;
	
	public int myEmulPort;
	public static final int MAX_EMULATORS = 3;
	public int[] ports;
	
	public NetServent(String loopbackIP, final int listenPort, int clientPort)
	{
		this.loopbackIP = loopbackIP;
		this.connectPort = clientPort;
		mainContext = GroupMessengerActivity.getContext();
		providerUri = Uri.parse("content://" + mainContext.getResources().getString(R.string.PROVIDER_URI));
		myEmulPort = getMyPort();
		clientSock = new HashMap<Integer, Socket>(MAX_EMULATORS);
//		writers = new HashMap<Socket, PrintWriter>(MAX_EMULATORS);
		objStrms = new HashMap<Socket, ObjectOutputStream>(MAX_EMULATORS);
		calculatePorts();
		
		new Thread(new Runnable() {
			public void run()
			{
				try {
					serverSock = new ServerSocket(listenPort);
				}catch(IOException e){
					Log.e("NetServent: Init ServerSocket","Could not establish listening socket on port " + listenPort);
				}
				
				while(!serverSock.isClosed())
				{
					try {
						Socket newSock = serverSock.accept();
						//There's no way to find out the client socket's
						//identity with regard to our pre-determined ports (11108, 11112, etc). If we were
						//working with distinct ip's, then we could use newSock.getRemoteSocketAddress.getAddress().
//						clientSock.put(newSock.getPort(), newSock);
//						writers.put(newSock, new PrintWriter(newSock.getOutputStream(), true));
						new Thread(new ReaderThread(newSock.getInputStream())).start();
					}catch(IOException e) {
						/* this is caught whether there is a legitimate socket issue, or if serverSock
						 * has been explicitly closed. Either way, readInput() is not reached, which is what we want.
						 */
						Log.d("NetServent: ServerSocket.accept()", e.getMessage());
					}
				}
			}}).start();
	}
	
		
	private void checkConnections()
	{
		Thread t;

		t = new Thread(new Runnable() {
			public void run() {
				try {
					for(int i = 0; i < MAX_EMULATORS; i++)
					{			
						if(!clientSock.containsKey(ports[i]) || !clientSock.get(ports[i]).isConnected())
						{
							clientSock.put(ports[i], new Socket(loopbackIP, ports[i]));
							/* Even if we were to use ReaderThreads on both sides of the socket, don't 
							 * create one on our own emulator instance... as it breaks proper reception of messages
							 * after the first message.
											new Thread(new ReaderThread(clientSock.get(port).getInputStream())).start();
							*/

							objStrms.put(clientSock.get(ports[i]), new ObjectOutputStream(clientSock.get(ports[i]).getOutputStream()));

						}
					}
				} catch (UnknownHostException e) {
					System.err.println("Don't know about host: " + loopbackIP + ".");
				} catch (IOException e) {
					System.err.println(e.getMessage());
				}

			}
		});
		t.start();
		try {
			t.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}
	
	public void sendMessage(Message msg)
	{
		checkConnections();
		
		try {
			for(Socket s : clientSock.values())
			{
				/* Reset object state for persistent object references. */
				objStrms.get(s).reset();
				objStrms.get(s).writeObject(msg);
//				writers.get(s).println(msg);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
		
	private class ReaderThread implements Runnable
	{
		private InputStream is;
		private ObjectInputStream ois;
		
		public ReaderThread(InputStream stream)
		{
			is = stream;
		}
		
		public void run()
		{
			try{
				ois = new ObjectInputStream(is);
				
				ContentValues cValues;
				Message m;
				ContentResolver cr = mainContext.getContentResolver();
				
				try {
					while((m = (Message)ois.readObject()) != null)
					{
					
						if(msgListener != null)
						{	
							msgListener.receiveMessage(m);

							cValues = new ContentValues();
							cValues.put("provider_key", String.valueOf(VectorClock.getSum(m.clock)));
							cValues.put("provider_value", m.message);
							cr.insert(providerUri, cValues);
						}	
						else
							Log.e("readInput", "MessageListener not initialized");
					}
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				ois.close();

			}catch (IOException e) {
				Log.e("readInput", e.getMessage());
			}
			
		}
	}
		
//	private class ReaderThread implements Runnable
//	{
//		private InputStream is;
//		private String inStr = "";
//		private BufferedReader in;
//
//		public ReaderThread(InputStream stream)
//		{
//			is = stream;
//		}
//		
//		public void run()
//		{
//			try{
//				in = new BufferedReader(new InputStreamReader(is));
//				ContentValues cValues;
//				int mCount = 0;
//				
//				while((inStr = in.readLine()) != null)
//				{
//					
//					if(msgListener != null)
//					{	
//						msgListener.receiveMessage(inStr + "\n");
//
//						cValues = new ContentValues();
//						cValues.put("provider_key", String.valueOf(mCount));
//						cValues.put("provider_value", inStr);
//						mainContext.getContentResolver().insert(providerUri, cValues);
//						mCount++;
//					}	
//					else
//						Log.e("readInput", "MessageListener not initialized");
//				}
//				
//				in.close();
//
//			}catch (IOException e) {
//				Log.e("readInput", e.getMessage());
//			}
//			
//		}
//	}

	private void reset()
	{
		try{
			for(ObjectOutputStream ois : objStrms.values())
			{
			ois.close();
			}

			for(Socket s : clientSock.values())
			{
				s.close();
			}
//			clientSock.shutdownInput();
		}catch(Exception e)
		{
			Log.d("NetServent.reset", e.getMessage());
		}
	}
	
	public void disconnect()
	{
		reset();
		try{
			serverSock.close();
		}catch(IOException e)
		{
			Log.d("NetServent.disconnect", e.getMessage());
		}		
	}
	
	public void setMessageListener(MessageListener ml)
	{
		msgListener = ml;
	}
	
	private void calculatePorts()
	{
		ports = new int[MAX_EMULATORS];
		int basePort = 5554;
		
		for(int i = 0; i < MAX_EMULATORS; i++)
		{
			ports[i] = (basePort + (2*i)) * 2;
		}
	}
	
	public int getIndexByPid(int pid)
	{
		for(int i=0; i < ports.length; i++)
		{
			if(ports[i] == pid)
				return i;
		}
		
		return -1;
	}
	
	private int getEmulPortByIndex(int i)
	{
		final int basePort = 5554;
		return (basePort + (2*i)) * 2;
	}
	
	private int getMyPort()
	{
		TelephonyManager tel = (TelephonyManager)mainContext.getSystemService(Context.TELEPHONY_SERVICE);
		return Integer.parseInt(tel.getLine1Number().substring(tel.getLine1Number().length() - 4)) * 2;
	}
}
